package cc.z8g.browser.html

import android.app.Activity
import android.graphics.Bitmap
import android.text.TextUtils
import androidx.core.net.toUri
import cc.z8g.browser.BrowserApp
import cc.z8g.browser.R
import cc.z8g.browser.core.BrowserConst
import cc.z8g.browser.database.Bookmark
import cc.z8g.browser.database.bookmark.BookmarkDAO
import cc.z8g.browser.extensions.safeUse
import cc.z8g.browser.favicon.FaviconModel
import cc.z8g.browser.favicon.toValidUri
import cc.z8g.browser.utils.FileUtils
import cc.z8g.browser.utils.ThemeUtils
import cc.z8g.browser.utils.ThreadPool
import io.reactivex.rxjava3.core.Single
import java.io.BufferedReader
import java.io.File
import java.io.FileOutputStream
import java.io.FileWriter
import java.io.IOException
import java.io.InputStreamReader
import java.util.Collections

class BookmarkFactory(private val activity: Activity) : AbsListPageFactory() {

    private val title = BrowserApp.getApplication().getString(R.string.page_bookmarks)
    private val folderIconFile by lazy { File(BrowserApp.getApplication().cacheDir, "folder.png") }
    private val defaultIconFile by lazy { File(BrowserApp.getApplication().cacheDir, "default.png") }

    private fun Int.toColor(): String {
        val string = Integer.toHexString(this)

        return string.substring(2) + string.substring(0, 2)
    }

    override fun createPage(): String {
        return buildPage().blockingGet()
    }

    override fun buildPage(): Single<String> = BookmarkDAO.getImpl()
        .getAllBookmarksSorted()
        .flattenAsObservable { it }
        .groupBy<Bookmark.Folder, Bookmark>(Bookmark.Entry::folder) { it }
        .flatMapSingle { bookmarksInFolder ->
            val folder = bookmarksInFolder.key
            return@flatMapSingle bookmarksInFolder
                .toList()
                .concatWith(
                    if (folder == Bookmark.Folder.Root) {
                        BookmarkDAO.getImpl().getFoldersSorted()
                            .map { it.filterIsInstance<Bookmark.Folder.Entry>() }
                    } else {
                        Single.just(emptyList())
                    }
                )
                .toList()
                .map { bookmarksAndFolders ->
                    Pair(folder, bookmarksAndFolders.flatten().map { it.asViewModel() })
                }
        }
        .map { (folder, viewModels) ->
            val content = construct(viewModels)
            Pair(folder, content)
        }
        .subscribeOn(ThreadPool.getDBScheduler())
        .observeOn(ThreadPool.getDiskScheduler())
        .doOnNext { (folder, content) ->
            FileWriter(createBookmarkPage(folder), false).use {
                it.write(content)
            }
        }
        .ignoreElements()
        .toSingle {
            cacheIcon(folderIconFile, ThemeUtils.createThemedBitmap(BrowserApp.getApplication(), R.drawable.ic_folder, ThemeUtils.getColor(activity, R.attr.autoCompleteTitleColor)))
            cacheIcon(defaultIconFile, FaviconModel.createDefaultBitmapForTitle(null))

            BrowserConst.FILE + createBookmarkPage(null)
        }

    private fun cacheIcon(file: File, icon: Bitmap) = FileOutputStream(file).safeUse {
        icon.compress(Bitmap.CompressFormat.PNG, 100, it)
        icon.recycle()
    }

    override fun getTemplateFilePath(): String = "html/bookmarks.html"

    override fun createPageFile(): File {
        TODO("Not yet implemented")
    }

    override fun assignTitle(): String = title

    override fun assignBoxBgColor(): String = ThemeUtils.getColor(activity, R.attr.autoCompleteBackgroundColor).toColor()

    override fun assignBoxTxtColor(): String = ThemeUtils.getColor(activity, R.attr.autoCompleteTitleColor).toColor()

    override fun assignBodyBgColor(): String = ThemeUtils.getColor(activity, R.attr.colorPrimary).toColor()
    override fun assignDividerColor(): String = ""

    override fun assignTitleColor(): String = ""

    override fun assignSubTitleColor(): String = ""

    override fun assignList(): MutableList<Item> = Collections.emptyList(); // 不用实现

    private fun construct(list: List<BookmarkViewModel>): String {
        val assets = BrowserApp.getApplication().assets
        val sb = StringBuilder()
        try {
            BufferedReader(InputStreamReader(assets.open(templateFilePath))).use { reader ->
                var beginTitle = true
                var beginStyle = true
                do {
                    val line = reader.readLine() ?: break
                    if (beginTitle && line.contains(VAR_TITLE)) {
                        beginTitle = false
                        val newLine = line.replace(VAR_TITLE, assignTitle())
                        sb.append(newLine).append("\n")
                    } else if (beginStyle) {
                        if (line.contains("</style>")) {
                            beginStyle = false
                            sb.append(line).append("\n")
                        } else {
                            val newLine = line.replace(VAR_BODY_BG_COLOR, assignBodyBgColor())
                                .replace(VAR_DIVIDER_COLOR, assignDividerColor())
                                .replace(VAR_BOX_BG_COLOR, assignBoxBgColor())
                                .replace(VAR_BOX_TXT_COLOR, assignBoxTxtColor())
                            sb.append(newLine).append("\n")
                        }
                    } else if (line.contains(VAR_ID_REAPEATED)) {
                        for (item in list) {
                            val newLine = line.replace(VAR_ITEM_HREF, item.url)
                                .replace(VAR_ITEM_TITLE, item.title)
                                .replace(VAR_ITEM_IMAGE_SRC, item.iconUrl)
                            sb.append(newLine).append("\n")
                        }
                    } else {
                        sb.append(line).append("\n")
                    }
                } while (true)
                return sb.toString()
            }
        } catch (ex: IOException) {
            return BrowserConst.FILE
        }
    }

    private fun Bookmark.asViewModel(): BookmarkViewModel = when (this) {
        is Bookmark.Folder -> createViewModelForFolder(this)
        is Bookmark.Entry -> createViewModelForBookmark(this)
    }

    private fun createViewModelForFolder(folder: Bookmark.Folder): BookmarkViewModel {
        return BookmarkViewModel(
            title = folder.title,
            url = BrowserConst.FILE + createBookmarkPage(folder),
            iconUrl = folderIconFile.toString()
        )
    }

    private fun createViewModelForBookmark(entry: Bookmark.Entry): BookmarkViewModel {
        val bookmarkUri = entry.url.toUri().toValidUri()

        val iconUrl = if (!TextUtils.isEmpty(entry.icon)) {
            entry.icon
        } else if (bookmarkUri != null) {
            val faviconFile = FaviconModel.getFaviconCacheFile(BrowserApp.getApplication(), bookmarkUri)
            if (!faviconFile.exists()) {
                val defaultFavicon = FaviconModel.createDefaultBitmapForTitle(entry.title)
                FaviconModel.cacheFaviconForUrl(defaultFavicon, entry.url)
                    .subscribeOn(ThreadPool.getDiskScheduler())
                    .subscribe()
            }
            faviconFile
        } else {
            defaultIconFile
        }

        return BookmarkViewModel(entry.title, entry.url, iconUrl.toString())
    }

    /**
     * Create the bookmark page file.
     */
    private fun createBookmarkPage(folder: Bookmark.Folder?): File {
        val prefix = if (folder?.title?.isNotBlank() == true) {
            "${folder.title}-"
        } else {
            ""
        }
        return File(File(BrowserApp.getApplication().filesDir, "bookmark"), prefix + BOOKMARKS_FILE_NAME).also {
            FileUtils.mkdirs(it.parentFile)
        }
    }

    data class BookmarkViewModel(
        val title: String,
        val url: String,
        val iconUrl: String
    )
}
